use actix_web::{
    dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform},
    Error, HttpMessage,
};
use futures_util::future::LocalBoxFuture;
use std::{
    future::{ready, Ready},
    rc::Rc,
};
use jsonwebtoken::{decode, DecodingKey, Validation, Algorithm};
use serde::{Deserialize, Serialize};
use uuid::Uuid;

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Claims {
    pub sub: Uuid,  // Subject (user ID)
    pub email: String,
    pub role: String,
    pub exp: usize, // Expiration time
    pub iat: usize, // Issued at
}

pub struct AuthMiddleware;

impl<S, B> Transform<S, ServiceRequest> for AuthMiddleware
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
    S::Future: 'static,
    B: 'static,
{
    type Response = ServiceResponse<B>;
    type Error = Error;
    type InitError = ();
    type Transform = AuthMiddlewareService<S>;
    type Future = Ready<Result<Self::Transform, Self::InitError>>;

    fn new_transform(&self, service: S) -> Self::Future {
        ready(Ok(AuthMiddlewareService {
            service: Rc::new(service),
        }))
    }
}

pub struct AuthMiddlewareService<S> {
    service: Rc<S>,
}

impl<S, B> Service<ServiceRequest> for AuthMiddlewareService<S>
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
    S::Future: 'static,
    B: 'static,
{
    type Response = ServiceResponse<B>;
    type Error = Error;
    type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;

    forward_ready!(service);

    fn call(&self, req: ServiceRequest) -> Self::Future {
        let service = self.service.clone();

        Box::pin(async move {
            // Skip authentication for certain paths
            let path = req.path();
            if should_skip_auth(path) {
                return service.call(req).await;
            }

            // Extract JWT token from Authorization header
            match extract_token(&req) {
                Ok(claims) => {
                    // Add claims to request extensions
                    req.extensions_mut().insert(claims);
                    service.call(req).await
                }
                Err(e) => {
                    tracing::warn!("Authentication failed: {}", e);
                    Err(actix_web::error::ErrorUnauthorized("Invalid token"))
                }
            }
        })
    }
}

fn should_skip_auth(path: &str) -> bool {
    let public_paths = [
        "/api/auth/login",
        "/api/auth/register",
        "/api/health",
        "/api/docs",
        "/favicon.ico",
    ];
    
    public_paths.iter().any(|&p| path.starts_with(p)) ||
    path.starts_with("/static/") ||
    path == "/"
}

fn extract_token(req: &ServiceRequest) -> Result<Claims, String> {
    let auth_header = req
        .headers()
        .get("Authorization")
        .ok_or("Missing Authorization header")?;

    let auth_str = auth_header
        .to_str()
        .map_err(|_| "Invalid Authorization header")?;

    if !auth_str.starts_with("Bearer ") {
        return Err("Invalid Authorization header format".to_string());
    }

    let token = &auth_str[7..]; // Remove "Bearer " prefix
    
    // Get JWT secret from app data
    let config = req
        .app_data::<actix_web::web::Data<crate::config::AppConfig>>()
        .ok_or("Missing app config")?;

    let decoding_key = DecodingKey::from_secret(config.jwt_secret.as_ref());
    let validation = Validation::new(Algorithm::HS256);

    let token_data = decode::<Claims>(token, &decoding_key, &validation)
        .map_err(|e| format!("Token validation failed: {}", e))?;

    Ok(token_data.claims)
}

// Helper function to get current user from request
pub fn get_current_user(req: &ServiceRequest) -> Option<Claims> {
    req.extensions().get::<Claims>().cloned()
}

// Helper function for handlers to get current user
pub fn get_user_claims(req: &actix_web::HttpRequest) -> Option<Claims> {
    req.extensions().get::<Claims>().cloned()
}

#[cfg(test)]
mod tests {
    use super::*;
    use actix_web::{test, web, App, HttpResponse};
    use jsonwebtoken::{encode, EncodingKey, Header};
    use crate::config::AppConfig;

    async fn protected_handler() -> HttpResponse {
        HttpResponse::Ok().json(serde_json::json!({"message": "protected"}))
    }

    #[actix_web::test]
    async fn test_auth_middleware_public_path() {
        let config = AppConfig::default();
        let app = test::init_service(
            App::new()
                .app_data(web::Data::new(config))
                .wrap(AuthMiddleware)
                .route("/api/auth/login", web::post().to(protected_handler))
        ).await;

        let req = test::TestRequest::post()
            .uri("/api/auth/login")
            .to_request();
            
        let resp = test::call_service(&app, req).await;
        assert!(resp.status().is_success());
    }

    #[actix_web::test]
    async fn test_auth_middleware_missing_token() {
        let config = AppConfig::default();
        let app = test::init_service(
            App::new()
                .app_data(web::Data::new(config))
                .wrap(AuthMiddleware)
                .route("/api/protected", web::get().to(protected_handler))
        ).await;

        let req = test::TestRequest::get()
            .uri("/api/protected")
            .to_request();
            
        let resp = test::call_service(&app, req).await;
        assert_eq!(resp.status(), 401);
    }
}